using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.Common;
using System.Threading;

using GURUCORE.Framework.Core;
using GURUCORE.Framework.DataAccess.DataSource;
using GURUCORE.Framework.DataAccess.Provider;

namespace GURUCORE.Framework.DataAccess
{
    /// <summary>
    /// provider for getting DbConnection, DbCommand
    /// </summary>    
    // [System.Diagnostics.DebuggerStepThrough]
	public sealed class DataAccessContext
	{
		private const string DATA_STORE_SLOT_NAME = "BusinessThreadContext";
        public const string DEFAULT_DATASOURCE = "";

		private Stack m_stkDataSource = new Stack();
		private Hashtable m_htTransaction = new Hashtable();
		private Hashtable m_htDefaultDataSource = new Hashtable();

        private string m_sUniqueID = Guid.NewGuid().ToString();
        public string UniqueID
        {
            get { return m_sUniqueID; }
            set { m_sUniqueID = value; }
        }

		private static object m_oLocker = new object();


        private int m_nTransactionCount = 0;
        public int TransactionCount
        {
            get { return m_nTransactionCount; }
        }

        private bool m_bTransactionProcessingFinished = false;
        /// <summary>
        /// TransactionProcessingCompleled always is false. Only after CommitTransaction or RollbackTransaction, TransactionProcessingCompleled will be true. So if TransactionCount = 0 and TransactionProcessingCompleled = fase, transaction is in starting point, if TransactionCount = 0 and TransactionProcessingCompleled = true, transaction is completed.
        /// </summary>
        public bool TransactionProcessingFinished
        {
            get { return m_bTransactionProcessingFinished; }
        }

        

        

		private DataAccessContext()
		{
			DataSourceFactory oDSFactory = (DataSourceFactory)GApplication.GetInstance().GetSharedObject(DataSourceFactory.GLOBAL_CACHE_KEY);
			ArrayList arrDSInfo = oDSFactory.GetItemNames();
			foreach (string sDSName in arrDSInfo)
			{
				string[] arrDSNameElement = sDSName.Split('#');
				if (arrDSNameElement.Length == 1)
				{
					if (!m_htDefaultDataSource.ContainsKey(string.Empty))
					{
						m_htDefaultDataSource.Add(string.Empty, sDSName);
					}
				}
				else if (arrDSNameElement.Length == 2)
				{
					if (!m_htDefaultDataSource.ContainsKey(arrDSNameElement[0]))
					{
						m_htDefaultDataSource.Add(arrDSNameElement[0], sDSName);
					}
				}
			}
		}

        public static DataAccessContext GetDataAccessContext()
        {
            LocalDataStoreSlot oSlot = Thread.GetNamedDataSlot(DATA_STORE_SLOT_NAME);
            DataAccessContext oDACtx;
            if (null == oSlot)
            {
                oSlot = Thread.AllocateNamedDataSlot(DATA_STORE_SLOT_NAME);
                oDACtx = new DataAccessContext();
                Thread.SetData(oSlot, oDACtx);
            }
            else
            {
                oDACtx = (DataAccessContext)Thread.GetData(oSlot);
                if (null == oDACtx)
                {
                    oDACtx = new DataAccessContext();
                    Thread.SetData(oSlot, oDACtx);
                }
            }
            return oDACtx;
        }



        

		public void SetCurrentDataSource(string p_sDataSource)
		{
			if (string.IsNullOrEmpty(p_sDataSource))
			{
                m_stkDataSource.Push(m_htDefaultDataSource[DEFAULT_DATASOURCE]);    //use system default
			}
			else
			{
				m_stkDataSource.Push(p_sDataSource);
			}
		}

		/// <summary>
		/// moduleID will be default datasource name
		/// </summary>
		/// <param name="p_sModuleID"></param>
		/// <param name="p_sDataSource"></param>
		public void SetCurrentDataSource(string p_sModuleID, string p_sDataSource)
		{
            if (string.IsNullOrEmpty(p_sDataSource))
			{
                m_stkDataSource.Push(m_htDefaultDataSource[p_sModuleID]);   //use module default
			}
			else
			{
				m_stkDataSource.Push(p_sModuleID + "#" + p_sDataSource);
			}
		}

		public string UnSetCurrentDataSource()
		{
			return (string)m_stkDataSource.Pop();
		}




		public void EnterTransaction()
		{
			//GURUCORE.Lib.Core.Logging.FileLogger.Pln("Enter Transaction (" + m_nTransactionCount + ") on " + System.Environment.StackTrace);
			m_nTransactionCount++;
			/*GURUCORE.Lib.Core.Logging.FileLogger.Pln("On Thread: " + Thread.CurrentThread.ManagedThreadId.ToString());
			GURUCORE.Lib.Core.Logging.FileLogger.Pln("DACtx UniqueID : " + m_sUniqueID);
			GURUCORE.Lib.Core.Logging.FileLogger.Pln("Enter Transaction (" + m_nTransactionCount + ") on " + (new System.Diagnostics.StackTrace()).ToString());*/
		}

		public void LeaveTransaction(bool p_bCommit)
		{
			m_nTransactionCount--;
			
			if (m_nTransactionCount < 0)
			{
				//throw new Exception("Inconsistency transaction stack");
				m_nTransactionCount = 0;
				m_htTransaction.Clear();
			}
			else
			{
				if (m_nTransactionCount == 0)
				{
					if (p_bCommit)
					{
						CommitTransaction();
					}
					else
					{
						RollbackTransaction();
					}

					m_htTransaction.Clear();
				}
			}
		}

        private void CommitTransaction()
        {
            if (m_nTransactionCount == 0)
            {
                foreach (DictionaryEntry oEntry in m_htTransaction)
                {
                    IDbTransaction oDbTran = (IDbTransaction)oEntry.Value;
                    IDbConnection oDbConn = oDbTran.Connection;
                    try
                    {
                        oDbTran.Commit();
                        if (oDbConn.State != ConnectionState.Closed)
                        {
                            oDbConn.Close();
                        }
                    }
                    catch (Exception ex)
                    {
                    }
                }
                m_bTransactionProcessingFinished = true;
            }
        }

        private void RollbackTransaction()
        {
            if (m_nTransactionCount == 0)
            {
                foreach (DictionaryEntry oEntry in m_htTransaction)
                {
                    IDbTransaction oDbTran = (IDbTransaction)oEntry.Value;
                    IDbConnection oDbConn = oDbTran.Connection;
                    try
                    {
                        oDbTran.Rollback();
                        if (oDbConn.State != ConnectionState.Closed)
                        {
                            oDbConn.Close();
                        }
                    }
                    catch (Exception ex)
                    {
                    }
                }
                m_bTransactionProcessingFinished = true;
            }
        }




		public IDbConnection GetConnection()
		{
			string sCurrentDataSource = (string)m_stkDataSource.Peek();
			//Console.WriteLine("Get " + sCurrentDataSource);
			if (m_nTransactionCount > 0) // dang trong mot transaction nao do, boc cha con goi nhau
			{
                if (null == m_htTransaction[sCurrentDataSource])
				{
					DataSourceFactory oDSFactory = (DataSourceFactory)GApplication.GetInstance().GetSharedObject(DataSourceFactory.GLOBAL_CACHE_KEY);
					string sProvider = oDSFactory.GetProvider(sCurrentDataSource);
					string sConnStr = oDSFactory.GetConnectionString(sCurrentDataSource);

					DataProviderFactory oDPFactory = (DataProviderFactory)GApplication.GetInstance().GetSharedObject(DataProviderFactory.GLOBAL_CACHE_KEY);
					IDbConnection oDbConn = oDPFactory.GetDbConnection(sConnStr, sProvider);

					oDbConn.Open();
					IDbTransaction oDbTran = oDbConn.BeginTransaction();

					m_htTransaction.Add(sCurrentDataSource, oDbTran);
					return oDbConn;
				}
				else
				{
					IDbTransaction oDbTran = (IDbTransaction)m_htTransaction[sCurrentDataSource];
					return oDbTran.Connection;
				}
			}
			else
			{
				DataSourceFactory oDSFactory = (DataSourceFactory)GApplication.GetInstance().GetSharedObject(DataSourceFactory.GLOBAL_CACHE_KEY);
				string sProvider = oDSFactory.GetProvider(sCurrentDataSource);
				string sConnStr = oDSFactory.GetConnectionString(sCurrentDataSource);

				DataProviderFactory oDPFactory = (DataProviderFactory)GApplication.GetInstance().GetSharedObject(DataProviderFactory.GLOBAL_CACHE_KEY);
				IDbConnection oDbConn = oDPFactory.GetDbConnection(sConnStr, sProvider);

				oDbConn.Open();

				//GURUCORE.Lib.Core.Logging.FileLogger.Pln("Get Connection without TransactionBound" + m_nTransactionCount.ToString());
				return oDbConn;
			}
		}

		public void ReturnConnection(IDbConnection p_oDbConn)
		{
			string sCurrentDataSource = (string)m_stkDataSource.Peek();
			if (m_nTransactionCount > 0)
			{

			}
			else
			{
				if (p_oDbConn.State != ConnectionState.Closed)
				{
					p_oDbConn.Close();
				}
			}			
		}

		

		public IDbCommand GetDbCommand(IDbConnection p_oDbConn)
		{
			string sCurrentDataSource = (string)m_stkDataSource.Peek();
			IDbCommand oDbCmd = p_oDbConn.CreateCommand();
			if (m_nTransactionCount > 0)
			{
                IDbTransaction transaction = m_htTransaction[sCurrentDataSource] as IDbTransaction;
				string sDebug = string.Empty;
                if (transaction == null)
				{
					foreach (DictionaryEntry oEntry in m_htTransaction)
					{
						try
						{
							sDebug = sDebug + ">>>>" + oEntry.Key + "=" + oEntry.Value + "\r\n";
						}
						catch
						{
							sDebug = sDebug + ">>>>" + oEntry.Key + "= null \r\n";
						}
					}

					// DEBUG: After inconsistency transaction stack
					throw new DataAccessException("m_htTransaction[sCurrentDataSource] null : " + sCurrentDataSource + "\r\n" + sDebug);

				}
                oDbCmd.Transaction = transaction;
			}
			return oDbCmd;
		}

	    public IDbDataAdapter GetDbDataAdapter()
        {
            const string DEFAULT_PROVIDER_INVARIANT_NAME = "System.Data.SqlClient";

            string sProviderInvariantName = this.GetProviderInvariantName();
            if (string.IsNullOrEmpty(sProviderInvariantName))
            {
                sProviderInvariantName = DEFAULT_PROVIDER_INVARIANT_NAME;
            }

            DbProviderFactory provider = DbProviderFactories.GetFactory(sProviderInvariantName);
            IDbDataAdapter dataadapter = provider.CreateDataAdapter();
            return dataadapter;
        }



        #region Getter of DataProvider.config

        public string GetProviderInvariantName()
        {
            string sCurrentDataSource = (string)m_stkDataSource.Peek();
            DataSourceFactory oDSFactory = (DataSourceFactory)GApplication.GetInstance().GetSharedObject(DataSourceFactory.GLOBAL_CACHE_KEY);
            string sProvider = oDSFactory.GetProvider(sCurrentDataSource);
            DataProviderFactory oDPFactory = (DataProviderFactory)GApplication.GetInstance().GetSharedObject(DataProviderFactory.GLOBAL_CACHE_KEY);
            string sReturn = oDPFactory.GetProviderInvariantName(sProvider);
            return sReturn;
        }

        public string GetUnicodeForm()
		{
            string sCurrentDataSource = (string)m_stkDataSource.Peek();
			DataSourceFactory oDSFactory = (DataSourceFactory)GApplication.GetInstance().GetSharedObject(DataSourceFactory.GLOBAL_CACHE_KEY);
			string sProvider = oDSFactory.GetProvider(sCurrentDataSource);
			DataProviderFactory oDPFactory = (DataProviderFactory)GApplication.GetInstance().GetSharedObject(DataProviderFactory.GLOBAL_CACHE_KEY);
			string sReturn = oDPFactory.GetUnicodeForm(sProvider);
			return sReturn;
		}

        public string GetFieldNameWrap()
        {
            string sCurrentDataSource = (string)m_stkDataSource.Peek();
            DataSourceFactory oDSFactory = (DataSourceFactory)GApplication.GetInstance().GetSharedObject(DataSourceFactory.GLOBAL_CACHE_KEY);
            string sProvider = oDSFactory.GetProvider(sCurrentDataSource);
            DataProviderFactory oDPFactory = (DataProviderFactory)GApplication.GetInstance().GetSharedObject(DataProviderFactory.GLOBAL_CACHE_KEY);
            string sReturn = oDPFactory.GetFieldNameWrap(sProvider);
            return sReturn;
        }

		public string GetPagingSelectTemplate()
		{
			string sCurrentDataSource = (string)m_stkDataSource.Peek();
			DataSourceFactory oDSFactory = (DataSourceFactory)GApplication.GetInstance().GetSharedObject(DataSourceFactory.GLOBAL_CACHE_KEY);
			string sProvider = oDSFactory.GetProvider(sCurrentDataSource);
			DataProviderFactory oDPFactory = (DataProviderFactory)GApplication.GetInstance().GetSharedObject(DataProviderFactory.GLOBAL_CACHE_KEY);
			string sReturn = oDPFactory.GetPagingSelectTemplate(sProvider);
			return sReturn;
		}

		public string GetInlineParameterForm()
		{
			string sCurrentDataSource = (string)m_stkDataSource.Peek();
			DataSourceFactory oDSFactory = (DataSourceFactory)GApplication.GetInstance().GetSharedObject(DataSourceFactory.GLOBAL_CACHE_KEY);
			string sProvider = oDSFactory.GetProvider(sCurrentDataSource);
			DataProviderFactory oDPFactory = (DataProviderFactory)GApplication.GetInstance().GetSharedObject(DataProviderFactory.GLOBAL_CACHE_KEY);
			string sReturn = oDPFactory.GetInlineParameterForm(sProvider);
			return sReturn;
		}

		public string GetParameterForm()
		{
			string sCurrentDataSource = (string)m_stkDataSource.Peek();
			DataSourceFactory oDSFactory = (DataSourceFactory)GApplication.GetInstance().GetSharedObject(DataSourceFactory.GLOBAL_CACHE_KEY);
			string sProvider = oDSFactory.GetProvider(sCurrentDataSource);
			DataProviderFactory oDPFactory = (DataProviderFactory)GApplication.GetInstance().GetSharedObject(DataProviderFactory.GLOBAL_CACHE_KEY);
			string sReturn = oDPFactory.GetParameterForm(sProvider);
			return sReturn;
		}

		public string GetLatestIdentityStatement()
		{
			string sCurrentDataSource = (string)m_stkDataSource.Peek();
			DataSourceFactory oDSFactory = (DataSourceFactory)GApplication.GetInstance().GetSharedObject(DataSourceFactory.GLOBAL_CACHE_KEY);
			string sProvider = oDSFactory.GetProvider(sCurrentDataSource);
			DataProviderFactory oDPFactory = (DataProviderFactory)GApplication.GetInstance().GetSharedObject(DataProviderFactory.GLOBAL_CACHE_KEY);
			string sReturn = oDPFactory.GetLatestIdentityStatement(sProvider);
			return sReturn;
        }

        #endregion

    }
}
